package com.anyext.storage.transaction;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.HeuristicCompletionException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.UnexpectedRollbackException;

import com.anyext.storage.StorageContext;
import com.anyext.storage.group.Group;
import com.anyext.storage.group.GroupDescriptor;

/**
 * @author mh
 * @since 14.02.11
 */
public class MultiTransactionManager implements PlatformTransactionManager {

	private final static Log logger = LogFactory.getLog(MultiTransactionManager.class);

	private static boolean initFlag = false;

	private final SynchronizationManager synchronizationManager;
	private final List<PlatformTransactionManager> transactionManagers;

	public MultiTransactionManager() {
		this(new DefaultSynchronizationManager());
	}

	public MultiTransactionManager(SynchronizationManager synchronizationManager) {
		this.synchronizationManager = synchronizationManager;
		this.transactionManagers = new ArrayList<PlatformTransactionManager>();
	}

	private void init() {
		Collection<String> groupNames = StorageContext.getGroupNames();
		for (String groupName : groupNames) {
			Group group = StorageContext.getGroup(groupName);
			Map<String, GroupDescriptor> descriptors = group.getGroupDescriptors();
			if (descriptors != null) {
				for (GroupDescriptor descriptor : descriptors.values()) {
					DataSourceTransactionManager txManager = new DataSourceTransactionManager(descriptor.getDataSource());
					transactionManagers.add(txManager);
				}
			}
		}
		Collections.reverse(transactionManagers);
		logger.info("init : " + transactionManagers.size());
		initFlag = true;
	}

	@Override
	public MultiTransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
		if (!initFlag)
			init();
		logger.info("getTransaction() : " + this.hashCode());
		MultiTransactionStatus mts = new MultiTransactionStatus(transactionManagers.get(0)/*
																						 * First
																						 * TM
																						 * is
																						 * main
																						 * TM
																						 */);

		if (!synchronizationManager.isSynchronizationActive()) {
			synchronizationManager.initSynchronization();
			mts.setNewSynchonization();
		}

		for (PlatformTransactionManager transactionManager : transactionManagers) {
			mts.registerTransactionManager(definition, transactionManager);
		}

		return mts;
	}

	@Override
	public void commit(TransactionStatus status) throws TransactionException {
		logger.info("commit()");
		MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;

		boolean commit = true;
		Exception commitException = null;
		PlatformTransactionManager commitExceptionTransactionManager = null;

		for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) {
			if (commit) {
				try {
					multiTransactionStatus.commit(transactionManager);
				} catch (Exception ex) {
					commit = false;
					commitException = ex;
					commitExceptionTransactionManager = transactionManager;
				}
			} else {
				// after unsucessfull commit we must try to rollback remaining
				// transaction managers
				try {
					multiTransactionStatus.rollback(transactionManager);
				} catch (Exception ex) {
					logger.warn("Rollback exception (after commit) (" + transactionManager + ") " + ex.getMessage(), ex);
				}
			}
		}

		if (multiTransactionStatus.isNewSynchonization()) {
			synchronizationManager.clearSynchronization();
		}

		if (commitException != null) {
			boolean firstTransactionManagerFailed = commitExceptionTransactionManager == getLastTransactionManager();
			int transactionState = firstTransactionManagerFailed ? HeuristicCompletionException.STATE_ROLLED_BACK : HeuristicCompletionException.STATE_MIXED;
			throw new HeuristicCompletionException(transactionState, commitException);
		}

	}

	@Override
	public void rollback(TransactionStatus status) throws TransactionException {
		logger.info("rollback()");
		Exception rollbackException = null;
		PlatformTransactionManager rollbackExceptionTransactionManager = null;

		MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;

		for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) {
			try {
				multiTransactionStatus.rollback(transactionManager);
			} catch (Exception ex) {
				if (rollbackException == null) {
					rollbackException = ex;
					rollbackExceptionTransactionManager = transactionManager;
				} else {
					logger.warn("Rollback exception (" + transactionManager + ") " + ex.getMessage(), ex);
				}
			}
		}

		if (multiTransactionStatus.isNewSynchonization()) {
			synchronizationManager.clearSynchronization();
		}

		if (rollbackException != null) {
			throw new UnexpectedRollbackException("Rollback exception, originated at (" + rollbackExceptionTransactionManager + ") " + rollbackException.getMessage(), rollbackException);
		}
	}

	private <T> Iterable<T> reverse(Collection<T> collection) {
		List<T> list = new ArrayList<T>(collection);
		Collections.reverse(list);
		return list;
	}

	private PlatformTransactionManager getLastTransactionManager() {
		return transactionManagers.get(lastTransactionManagerIndex());
	}

	private int lastTransactionManagerIndex() {
		return transactionManagers.size() - 1;
	}

}